//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-09-18     ZhangYihua   first version
//
// Description  : (a + j*b)(coswt +/- j*sinwt) = c + j*d  => a^2 + b^2 = c^2 + d^2
//################################################################################

module mixer #(
parameter           DW                      = 8,
parameter           CW                      = 12,
parameter           FREQ_DOWN               = 1'b1,     // 1'b1:down(Fo=Fi-Fnco); 1'b0:up(Fo=Fi+Fnco)
parameter           PWR_LIMITED             = 1'b0,     // set it to 1'b1 if power of din<=(2^(DW-1) -1)^2 for better timing
parameter           NCO_DW                  = 16,
parameter           PHS_DW                  = 11        // 3< PHS_DW <=NCO_DW
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input       signed  [DW-1:0]                din_i,
input       signed  [DW-1:0]                din_q,

output      signed  [DW-1:0]                dout_i,
output      signed  [DW-1:0]                dout_q,

input       signed  [NCO_DW-1:0]            freq_word   // frequency of sin/cos is F_clk*freq_word/(2^NCO_DW)
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          WAVE_AM                 = (2**(CW-1))-1;
localparam          PW                      = DW+CW+1;

wire        signed  [CW-1:0]                cos_dat;
wire        signed  [CW-1:0]                sin_dat;

wire        signed  [PW-1:0]                prdc_i;
wire        signed  [PW-1:0]                prdc_q;

//################################################################################
// main
//################################################################################

cos_sin_wave #(
        .WAVE_DW                        (CW                             ),
        .WAVE_AM                        (WAVE_AM                        ),	// WAVE_AM < 2^(WAVE_DW-1)
        .NEG_SIN                        (FREQ_DOWN                      ),	// 1'b0:normal; 1'b1:invert sin;
        .NCO_DW                         (NCO_DW                         ),
        .PHS_DW                         (PHS_DW                         )	// 3< PHS_DW <=NCO_DW
) u_cos_sin_wave ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .freq_word                      (freq_word                      ),	// output frequency is F_clk*freq_word/(2^NCO_DW)

        .cos_dat                        (cos_dat                        ),  // (CW, CW-1)
        .sin_dat                        (sin_dat                        )   // (CW, CW-1)
);

complex_mul #(
        .AW                             (DW                             ),
        .BW                             (CW                             )
) u_dxejwt ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .ar                             (din_i                          ),	// real      part of a
        .ai                             (din_q                          ),	// imaginary part of a
        .br                             (cos_dat                        ),	// real      part of b
        .bi                             (sin_dat                        ),	// imaginary part of b

        .pr                             (prdc_i                         ),	// real      part of p
        .pi                             (prdc_q                         )	// imaginary part of p
);
// the width of prdc_i/prdc_q <= PW-1, because -1.0 < cos_dat/sin_dat < 1.0

generate if (PWR_LIMITED==1'b1) begin:G_LIMITED
    ds_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (PW-2                           ),	// input data width
        .IFW                            (CW-1                           ),	// input fractional width,  IFW>=IDW is legal
        .ODW                            (DW                             ),	// output data width
        .OFW                            (0                              )	// output fractional width, OFW>=ODW is legal
    ) u_sat_sru ( 
        .id_re                          (prdc_i[PW-3:0]                 ),	// s(IDW, IFW), the MSB is sign
        .id_im                          (prdc_q[PW-3:0]                 ),	// s(IDW, IFW), the MSB is sign
        .od_re                          (dout_i                         ),	// s(ODW, OFW), the MSB is sign
        .od_im                          (dout_q                         ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
    );
end else begin:G_NLIMITED
    ds_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (PW-1                           ),	// input data width
        .IFW                            (CW-1                           ),	// input fractional width,  IFW>=IDW is legal
        .ODW                            (DW                             ),	// output data width
        .OFW                            (0                              )	// output fractional width, OFW>=ODW is legal
    ) u_sat_sru ( 
        .id_re                          (prdc_i[PW-2:0]                 ),	// s(IDW, IFW), the MSB is sign
        .id_im                          (prdc_q[PW-2:0]                 ),	// s(IDW, IFW), the MSB is sign
        .od_re                          (dout_i                         ),	// s(ODW, OFW), the MSB is sign
        .od_im                          (dout_q                         ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
    );
end endgenerate

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

a_pi_high_nc: assert property (@(posedge clk) disable iff (!rst_n)
    (PWR_LIMITED==1'b1) |-> ((prdc_i[PW-1-:3]==3'b000) || (prdc_i[PW-1-:3]==3'b111))
) else begin
    $error("pi_high is not same.");
end

a_pq_high_nc: assert property (@(posedge clk) disable iff (!rst_n)
    (PWR_LIMITED==1'b1) |-> ((prdc_q[PW-1-:3]==3'b000) || (prdc_q[PW-1-:3]==3'b111))
) else begin
    $error("pq_high is not same.");
end

// synopsys translate_on
`endif

endmodule
